Sagemaker Serverless Inference (まだPreview)で独自のアルゴリズムで学習した骨格検知モデルをリアルタイム推論してみた
せーのでございます。
先日、骨格検知のスタンダードでもあるOpenposeを変更して軽くしたアルゴリズム「Real-time 2D Multi-Person Pose Estimation on CPU: Lightweight OpenPose」をSagemakerコンテナに注入して学習したモデルを使って推論処理をしてみました。
今日はこのアルゴリズムを去年のRe:Invent 2021で発表されたばかりの新機能「Sagemaker Serverless Inference」を使ってサーバレス化してみたいと思います。
Sagemaker Serverless Inferenceとは
Sagemaker Serverless Inferenceとはその名の通り、Sagemakerを使ったリアルタイム推論をサーバレスで行えるサービスです。リクエストが来たときだけAWS側でサーバを立ち上げて推論処理をしてくれるので余計なコストの削減につながります。
現在はまだプレビュー中で限られたリージョンでのみ使用できますが、断続的な推論のリクエストが来るケースなど使いみちは多そうですね。
SDKはまだ整備途中
前回のブログではAWSが提供するSagemaker SDKを使って推論ロジックを作っていたのでそのまま流用したいと思っていたのですが、実際ロジックを組んでいくと
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-7-d576fe1e4d54> in <module> 19 20 # デプロイ ---> 21 predictor = serverless_model.deploy(**deploy_params) ~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/model.py in deploy(self, initial_instance_count, instance_type, serializer, deserializer, accelerator_type, endpoint_name, tags, kms_key, wait, data_capture_config, serverless_inference_config, **kwargs) 797 self._base_name = "-".join((self._base_name, compiled_model_suffix)) 798 --> 799 self._create_sagemaker_model(instance_type, accelerator_type, tags) 800 801 serverless_inference_config_dict = ( ~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/model.py in _create_sagemaker_model(self, instance_type, accelerator_type, tags) 273 /api/latest/reference/services/sagemaker.html#SageMaker.Client.add_tags 274 """ --> 275 container_def = self.prepare_container_def(instance_type, accelerator_type=accelerator_type) 276 277 self._ensure_base_name_if_needed(container_def["Image"]) ~/anaconda3/envs/pytorch_latest_p36/lib/python3.6/site-packages/sagemaker/pytorch/model.py in prepare_container_def(self, instance_type, accelerator_type) 231 if instance_type is None: 232 raise ValueError( --> 233 "Must supply either an instance type (for choosing CPU vs GPU) or an image URI." 234 ) 235 ValueError: Must supply either an instance type (for choosing CPU vs GPU) or an image URI.
このように「インスタンスタイプが設定されていないよ」というエラーが出ました。サーバレスなのでインスタンスタイプは設定していないのですが、どうやらそれは許されない様子。
元となるSDKがあるGithubを見てみると
https://github.com/aws/sagemaker-python-sdk/pull/2831
Add support for Liberty feature aka serverless inference:
Add sagemaker.serverless.serverless_inference_config as the configuration class for serverless inference
Add support in sagemaker.estimator, sagemaker.model, sagemaker.tensorflow.model to let users deploy serverless endpoint by passing the configuration object.
3.Add support in sagemaker.session to generate production variants with serverless config and also create endpoints using serverless configurations.
Detailed Design is in this doc
どうやら前回使っていたPytorchModelに対してはまだServerless Inferenceの実装がされていないようです。
ですのでboto3を使ってAPIを叩いていくことで実装することにしました。
流れ
APIを使ったServerless Inferenceの実装はこのようになります。
整理すると
- モデルを作る
- エンドポイント設定を作る(ここでサーバレスを指定)
- エンドポイントを作る
- 呼び出す
という流れです。基本は通常のSagemakerのリアルタイム推論サーバを構築する手段と変わらないですね。ではやっていきましょう。
やってみた
まずは構築用のSagemaker Notebooksを作成します。Pytorchを使うので「conda_pytorch_latest_p36」を選びました。Sagemakerのリージョンはap-northeast-1(東京)です。
最新の機能を使うのでライブラリを最新にアップデートしておきます。
import sys import IPython install_needed = True # Set to True to upgrade if install_needed: print("installing deps and restarting kernel") !{sys.executable} -m pip install -U pip !{sys.executable} -m pip install -U sagemaker IPython.Application.instance().kernel.do_shutdown(True)
Sagemakerライブラリとboto3をインポートし
import sagemaker import boto3 sagemaker.__version__ #client setup client = boto3.client(service_name="sagemaker") runtime = boto3.client(service_name="sagemaker-runtime")
'2.74.0'
IAMロールを取得します。
from sagemaker import get_execution_role role = get_execution_role()
これで準備は完了です。
CreateModelAPIでモデルを作成
まずはモデルを作成します。
ここで重要なのはAPIにはSDKのように推論に使うライブラリを入れるプロパティがありません。
SDKでは下のコードのようにモデルのパスの他に推論コードに必要なライブラリを入れるsource_dir
という引数があるのですが、APIにはありません。
from sagemaker.pytorch.model import PyTorchModel s3_path="s3://XXXXXXXap-northeast-1/outputs/sagemaker-pytorchXXXXXXXXX/output/model.tar.gz" pytorch_model = sagemaker.pytorch.model.PyTorchModel(model_data=s3_path, role=role, framework_version='1.4.0', py_version="py3", source_dir='inference', entry_point="inf.py")
ですので、APIではモデルを固めているmodel.tar.gzファイルの中に推論コードを入れ込んで、まとめて固めます。
前回のSDKを使って推論サーバを作った際にできたモデルをマネージメントコンソールから確認してみます。
コード上で指定していたS3のパスと「モデルデータの場所」として指定されているS3のパスが違いました。つまりSDKの中でコード上のS3パスからダウンロードしてきて、コードを入れて固め直しているかと思います。
※後ほどSDKのコードを確認してみた所、固め直しているような場所を発見しました。
この固め直したパスから直接model.tar.gzをダウンロードして、解凍してみます。
学習したモデルと同じレベルでcode
というディレクトリが作られ、その中にsource_dir
の中身がまるっと入っていました。つまり、これと同じものを作ってAPIのmodelを指定するパスに入れればOKのようです。今回は固め直すコードは省略して、このSDKで作られたmodel.tar.gzを使いたいと思います(3分クッキング風)。
boto_session = boto3.session.Session() region = boto_session.region_name image_uri = sagemaker.image_uris.retrieve( framework='pytorch', region=region, version='1.4.0', py_version="py3", image_scope='inference', instance_type='ml.t2.medium' ) s3_repack_path="s3://sagemaker-ap-northeast-1-XXXXXXXXX/pytorch-inference-2022-02-03-10-09-31-058/model.tar.gz" model_name = "ServerlessTest" # dummy environment variables byo_container_env_vars = {"SAGEMAKER_CONTAINER_LOG_LEVEL": "20", "SAGEMAKER_PROGRAM": "inf.py", "SAGEMAKER_REGION": region } create_model_response = client.create_model( ModelName=model_name, Containers=[ { "Image": image_uri, "Mode": "SingleModel", "ModelDataUrl": s3_repack_path, "Environment": byo_container_env_vars, } ], ExecutionRoleArn=role, ) print("Model Arn: " + create_model_response["ModelArn"])
Model Arn: arn:aws:sagemaker:ap-northeast-1:XXXXXXXXXXXXX:model/ServerlessTest
推論サーバの元となるコンテナはフレームワークの種類やバージョンが決められていますので、こちらを参考に推論に必要なコンテナを選択しましょう。決めたらsagemaker.image_uris.retrieve
メソッドを使ってコンテナのimage_uriを取得します。もちろんこのリンクから直接コピーして文字列で書き込んでもOKです。
その他の環境変数はSDKで作られたモデルと同じものを使いました。これで無事モデルの完成です。
CreateEndpointConfigでServerlessConfigを設定
次にエンドポイント設定を作ります。これはGUIでもできるようですが、せっかくなのでコードで書いてみたいと思います。
epc_name="ServerlessTestConfig" endpoint_config_response = client.create_endpoint_config( EndpointConfigName=epc_name, ProductionVariants=[ { "VariantName": "AllTraffic", "ModelName": model_name, "ServerlessConfig": { "MemorySizeInMB": 4096, "MaxConcurrency": 1, }, }, ], ) print("Endpoint Configuration Arn: " + endpoint_config_response["EndpointConfigArn"])
Endpoint Configuration Arn: arn:aws:sagemaker:ap-northeast-1:XXXXXXXXXXXX:endpoint-config/ServerlessTestConfig
ここでサーバレスに関する設定(ServerlessConfig)をするのが今回の最大ポイントとなります。
サーバレスに関する設定は
- メモリサイズ: 1024 MB、2048 MB、3072 MB、4096 MB、5120 MB、6144MBから選択します。メモリサイズによって使えるvCPUの数が違い、値段もかわります。
- 同時接続数: このコンテナが同時に立ち上げられる最大数を設定します。エンドポイントごとの最大は50まで、アカウントのリージョンごとにすべてのサーバーレスエンドポイント間で共有できる同時実行性の合計は200となります。これは上限緩和が可能なようなので、やり方がよくわからない方はぜひメンバーズを利用して上限緩和をしてみてください。
の2つです。ちなみにサーバレス推論で与えられるストレージは5GBとなります。
CreateEndpointでエンドポイントを作成
最後に今までの設定を使ってエンドポイントを作ります。
endpoint_name = "ServerlessTestEndpoint" create_endpoint_response = client.create_endpoint( EndpointName=endpoint_name, EndpointConfigName=epc_name, ) print("Endpoint Arn: " + create_endpoint_response["EndpointArn"]) # wait for endpoint to reach a terminal state (InService) using describe endpoint import time describe_endpoint_response = client.describe_endpoint(EndpointName=endpoint_name) while describe_endpoint_response["EndpointStatus"] == "Creating": describe_endpoint_response = client.describe_endpoint(EndpointName=endpoint_name) print(describe_endpoint_response["EndpointStatus"]) time.sleep(15) describe_endpoint_response
エンドポイントの作成には数分かかるため、経緯がわかるようにステータスを表示させるロジックを下に書いてあります。
これでサーバレス推論の準備が整いました。
invoke_endpointでエンドポイントを呼び出す
では実際に呼び出してみたいと思います。ここは前回のブログと同じものになります。
まずテスト用にアップしていた画像を読み込み、確認として表示します。
import matplotlib.pyplot as plt import numpy as np import cv2 filename = 'test/test.jpg' # インライン表示 %matplotlib inline # Read image into memory payload = None with open(filename, 'rb') as f: payload = f.read() img = cv2.imdecode(np.frombuffer(payload, dtype='uint8'), cv2.IMREAD_UNCHANGED) #画像の表示 plt.imshow(img) plt.show()
そしてエンドポイント名を指定して推論処理を行います。
import json
Request.
response = runtime.invoke_endpoint( EndpointName=endpoint_name, ContentType='application/x-image', Accept='application/json', Body=bytearray(payload) )
response_dict = json.loads(response['Body'].read().decode("utf-8")) print(json.dumps(response_dict)) [/pytnon]
{"results": [[[585, 176, 0.7670751214027405], [562, 187, 0.9505834579467773], [588, 195, 0.9093048572540283], [581, 172, 0.28749969601631165], [570, 153, 0.5822606086730957], [532, 176, 0.9301615357398987], [525, 131, 0.8460462093353271], [532, 93, 0.6852066516876221]], [[270, 165, 0.8985937237739563], [285, 183, 0.9405729174613953], [311, 180, 0.8065033555030823], [330, 146, 0.8526697158813477], [315, 112, 0.9707766175270081], [258, 187, 0.8102609515190125], [255, 172, 0.5221396088600159], [258, 172, 0.3417767882347107]], [[382, 138, 0.962157130241394], [390, 180, 0.9582875967025757], [438, 176, 0.8330955505371094], [450, 198, 0.7568647265434265], [427, 202, 0.43543940782546997], [345, 187, 0.9023558497428894], [348, 210, 0.3467266857624054], [356, 198, 0.1827089786529541]]]}
無事、推論が成功しました!
注意点
前回SDKで作ったときよりもレスポンスが遅いかな、と感じました。
サーバレスなので、しばらく使わないとコールドスタート(プロビジョニングから始まるので処理に時間がかかる)になります。CloudwatchからSagemakerのModelSetupTimeを見るとコールドスタートの時間を測ることができます。
ですが私の場合ログを確認したところ、そもそもの推論にかかる時間がSDKで作ったときよりもかかっているようです。
ですので
- メモリサイズを上げる
- コンテナに指定したターゲットインスタンスタイプを変える
- GPU対応にする
などしたらもう少し早くなるかな、と思っています。
まとめ
以上、Sagemakerの新機能、Sagemaker Serverless Inferenceを使って独自アルゴリズムをリアルタイム推論してみました。サーバレス化自体はAPIを使えばそんなに難しい処理ではないので、ぜひ試してみていただければと思います。
参考リンク
- https://docs.aws.amazon.com/sagemaker/latest/dg/serverless-endpoints.html
- https://aws.amazon.com/jp/blogs/machine-learning/deploying-ml-models-using-sagemaker-serverless-inference-preview/
- https://docs.aws.amazon.com/sagemaker/latest/dg/monitoring-cloudwatch.html#cloudwatch-metrics-endpoint-invocation